package cn.airfei.aircore.core.aspect;

import cn.airfei.aircore.core.annotations.RedissonLock;
import cn.airfei.aircore.core.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * @description: 给方法加锁
 * @author: air
 * @create: 2021-01-30 17:16
 */
@Aspect
@Component
@Slf4j
public class RedissonLockAspect {
    private final static Integer CODE = 722001;

    private final static String ERROR_MSG = "您请求过于频繁,系统忙不过来了,请稍后再试试";

    private final static String ATTRIBUTE_KEY = "air_core_redisson_key_md5";

    @Resource
    private HttpServletRequest httpServletRequest;

    @Resource
    private RedissonClient redissonClient;

    @Pointcut("@annotation(cn.airfei.aircore.core.annotations.RedissonLock)")
    public void annotationPointcut() {

    }

    @Before(value = "annotationPointcut()")
    public void before(JoinPoint jp) {
        String keyMd5 = setRedisKeyMd5(jp);
        RLock lock = redissonClient.getLock(keyMd5);
        try {
            boolean tryLock = lock.tryLock(120, 60, TimeUnit.SECONDS);
            if (!tryLock) {
                log.warn("成功拦截重复请求,url:{},method:{},ip:{}", httpServletRequest.getRequestURI(), httpServletRequest.getMethod(), httpServletRequest.getRemoteAddr());
                throw new AppException(CODE, ERROR_MSG);
            }
        } catch (InterruptedException e) {
            log.error("tryLock 发生异常了,{}", e.getMessage());
            lock.unlock();
            throw new AppException(CODE, e.getMessage());
        }
    }

    @AfterReturning(value = "annotationPointcut()", returning = "result")
    public void afterReturning(JoinPoint jp, Object result) {
        String keyMd5 = (String) httpServletRequest.getAttribute(ATTRIBUTE_KEY);
        RLock lock = redissonClient.getLock(keyMd5);
        lock.unlock();
    }

    @AfterThrowing(value = "annotationPointcut()", throwing = "e")
    public void afterThrowing(JoinPoint jp, Exception e) {
        String keyMd5 = (String) httpServletRequest.getAttribute(ATTRIBUTE_KEY);
        RLock lock = redissonClient.getLock(keyMd5);
        lock.unlock();
    }


    /**
     * 设置redis key
     * key:所有参数+url+method+ip的MD5值
     *
     * @param jp
     * @return
     */
    private String setRedisKeyMd5(JoinPoint jp) {
        RedissonLock redissonLock = ((MethodSignature) jp.getSignature()).getMethod().getAnnotation(RedissonLock.class);
        StringBuilder lockName = new StringBuilder();
        lockName.append(httpServletRequest.getMethod());
        lockName.append(httpServletRequest.getRequestURL());

        // 是否指定了key
        if (!redissonLock.lockKey().equals("")) {
            log.info("lockKey :{}", redissonLock.lockKey());
            httpServletRequest.setAttribute(ATTRIBUTE_KEY, redissonLock.lockKey());
            return redissonLock.lockKey();
        }

        // 获取lockFields中的传参值
        Object[] objects = jp.getArgs();
        String[] lockFieldArr = StringUtils.isNotEmpty(redissonLock.lockFields()) ? redissonLock.lockFields().split(",") : new String[0];
        // 如果没有指定字段，则用所有参数值
        if (lockFieldArr.length == 0) {
            lockName.append(Arrays.toString(objects));
        }

        for (String lockField : lockFieldArr) {
            for (Object o : objects) {
                boolean mark = false;
                Class<?> aClass = o.getClass();
                Field[] declaredFields = aClass.getDeclaredFields();
                for (Field field : declaredFields) {
                    if (field.getName().equals(lockField)) {
                        try {
                            field.setAccessible(true);
                            Object o1 = field.get(o);
                            lockName.append(o1);
                            mark = true;
                            break;
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
                if (mark) {
                    break;
                }
            }
        }

        String md5Hex = DigestUtils.md5Hex(lockName.toString());
        log.info("lockName md5编码前:{}", lockName.toString());
        httpServletRequest.setAttribute(ATTRIBUTE_KEY, md5Hex);
        return md5Hex;
    }


}
